﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Threading;
using System.Globalization;

namespace Lilium.Web.Mvc
{
	public class CustomActionInvoker
	{
		public CustomActionInvoker()
		{
		}

		public void InvokeAction(ActionDescriptor actionDescriptor, ControllerContext controllerContext)
		{
			if (actionDescriptor == null) throw new ArgumentNullException("actionDescriptor");
			if (controllerContext == null) throw new ArgumentNullException("controllerContext");

			FilterInfo filters = this.GetFilters(controllerContext, actionDescriptor);

			try
			{
				AuthorizationContext context = this.InvokeAuthorizationFilters(controllerContext, filters.AuthorizationFilters, actionDescriptor);
				if (context.Result != null)
				{
					this.InvokeActionResult(controllerContext, context.Result);
				}
				else
				{
					if (controllerContext.Controller.ValidateRequest)
					{
						ValidateRequest(controllerContext);
					}
					IDictionary<string, object> parameterValues = this.GetParameterValues(controllerContext, actionDescriptor);
					ActionExecutedContext context2 = this.InvokeActionMethodWithFilters(controllerContext, filters.ActionFilters, actionDescriptor, parameterValues);
					this.InvokeActionResultWithFilters(controllerContext, filters.ResultFilters, context2.Result);
				}
			}
			catch (ThreadAbortException)
			{
				throw;
			}
			catch (Exception exception)
			{
				ExceptionContext context3 = this.InvokeExceptionFilters(controllerContext, filters.ExceptionFilters, exception);
				if (!context3.ExceptionHandled)
				{
					throw;
				}
				this.InvokeActionResult(controllerContext, context3.Result);
			}
		}

		protected virtual ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object actionReturnValue)
		{
			if (actionReturnValue == null)
			{
				return new EmptyResult();
			}
			return ((actionReturnValue as ActionResult) ?? new ContentResult { Content = Convert.ToString(actionReturnValue, CultureInfo.InvariantCulture) });
		}


		protected virtual ExceptionContext InvokeExceptionFilters(ControllerContext controllerContext, IList<IExceptionFilter> filters, Exception exception)
		{
			ExceptionContext filterContext = new ExceptionContext(controllerContext, exception);
			foreach (IExceptionFilter filter in filters)
			{
				filter.OnException(filterContext);
			}
			return filterContext;
		}

		protected virtual ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
		{//TODO: какая-то мутная хрень, надо переписать
			ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);
			Func<ActionExecutedContext> continuation = () =>
				new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */)
				{
					Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters)
				};

			// need to reverse the filter list because the continuations are built up backward
			Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation,
				(next, filter) => () => InvokeActionMethodFilter(filter, preContext, next));
			return thunk();
		}

		protected virtual ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
		{
			object actionReturnValue = actionDescriptor.Execute(controllerContext, parameters);
			return this.CreateActionResult(controllerContext, actionDescriptor, actionReturnValue);
		}

		protected virtual ResultExecutedContext InvokeActionResultWithFilters(ControllerContext controllerContext, IList<IResultFilter> filters, ActionResult actionResult)
		{
			ResultExecutingContext preContext = new ResultExecutingContext(controllerContext, actionResult);
			Func<ResultExecutedContext> continuation = delegate
			{
				InvokeActionResult(controllerContext, actionResult);
				return new ResultExecutedContext(controllerContext, actionResult, false /* canceled */, null /* exception */);
			};

			// need to reverse the filter list because the continuations are built up backward
			Func<ResultExecutedContext> thunk = filters.Reverse().Aggregate(continuation,
				(next, filter) => () => InvokeActionResultFilter(filter, preContext, next));
			return thunk();
		}

		protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
		{
			actionResult.ExecuteResult(controllerContext);
		}

		protected virtual AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor)
		{
			AuthorizationContext filterContext = new AuthorizationContext(controllerContext, actionDescriptor);
			foreach (IAuthorizationFilter filter in filters)
			{
				filter.OnAuthorization(filterContext);
				if (filterContext.Result != null)
				{
					return filterContext;
				}
			}
			return filterContext;
		}

		protected virtual FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
		{
			FilterInfo filters = actionDescriptor.GetFilters();
			ControllerBase controller = controllerContext.Controller;
			AddControllerToFilterList<IActionFilter>(controller, filters.ActionFilters);
			AddControllerToFilterList<IResultFilter>(controller, filters.ResultFilters);
			AddControllerToFilterList<IAuthorizationFilter>(controller, filters.AuthorizationFilters);
			AddControllerToFilterList<IExceptionFilter>(controller, filters.ExceptionFilters);
			return filters;
		}

		protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
		{
			Type parameterType = parameterDescriptor.ParameterType;
			IModelBinder modelBinder = this.GetModelBinder(parameterDescriptor);
			IValueProvider valueProvider = controllerContext.Controller.ValueProvider;
			string str = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
			Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);
			ModelBindingContext bindingContext = new ModelBindingContext
			{
				FallbackToEmptyPrefix = parameterDescriptor.BindingInfo.Prefix == null,
				ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
				ModelName = str,
				ModelState = controllerContext.Controller.ViewData.ModelState,
				PropertyFilter = propertyFilter,
				ValueProvider = valueProvider
			};
			return (modelBinder.BindModel(controllerContext, bindingContext) ?? parameterDescriptor.DefaultValue);
		}

		protected virtual IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
			foreach (ParameterDescriptor descriptor in actionDescriptor.GetParameters())
			{
				dictionary[descriptor.ParameterName] = this.GetParameterValue(controllerContext, descriptor);
			}
			return dictionary;
		}

		#region protected ModelBinderDictionary Binders

		private ModelBinderDictionary m_Binders;

		protected ModelBinderDictionary Binders
		{
			get
			{
				if (this.m_Binders == null)
				{
					this.m_Binders = global::System.Web.Mvc.ModelBinders.Binders;
				}
				return this.m_Binders;
			}
			set
			{
				this.m_Binders = value;
			}
		}

		#endregion

		private static ActionExecutedContext InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func<ActionExecutedContext> continuation)
		{
			filter.OnActionExecuting(preContext);
			if (preContext.Result != null)
			{
				return new ActionExecutedContext(preContext, preContext.ActionDescriptor, true, null) { Result = preContext.Result };
			}
			bool flag = false;
			ActionExecutedContext filterContext = null;
			try
			{
				filterContext = continuation();
			}
			catch (ThreadAbortException)
			{
				filterContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false, null);
				filter.OnActionExecuted(filterContext);
				throw;
			}
			catch (Exception exception)
			{
				flag = true;
				filterContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false, exception);
				filter.OnActionExecuted(filterContext);
				if (!filterContext.ExceptionHandled)
				{
					throw;
				}
			}
			if (!flag)
			{
				filter.OnActionExecuted(filterContext);
			}
			return filterContext;
		}

		internal static ResultExecutedContext InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func<ResultExecutedContext> continuation)
		{
			filter.OnResultExecuting(preContext);
			if (preContext.Cancel)
			{
				return new ResultExecutedContext(preContext, preContext.Result, true, null);
			}
			bool flag = false;
			ResultExecutedContext filterContext = null;
			try
			{
				filterContext = continuation();
			}
			catch (ThreadAbortException)
			{
				filterContext = new ResultExecutedContext(preContext, preContext.Result, false, null);
				filter.OnResultExecuted(filterContext);
				throw;
			}
			catch (Exception exception)
			{
				flag = true;
				filterContext = new ResultExecutedContext(preContext, preContext.Result, false, exception);
				filter.OnResultExecuted(filterContext);
				if (!filterContext.ExceptionHandled)
				{
					throw;
				}
			}
			if (!flag)
			{
				filter.OnResultExecuted(filterContext);
			}
			return filterContext;
		}

		private static void ValidateRequest(ControllerContext controllerContext)
		{
			if (!controllerContext.IsChildAction)
			{
				controllerContext.HttpContext.Request.ValidateInput();
				string rawUrl = controllerContext.HttpContext.Request.RawUrl;
			}
		}

		private static void AddControllerToFilterList<TFilter>(ControllerBase controller, IList<TFilter> filterList) where TFilter : class
		{
			TFilter item = controller as TFilter;
			if (item != null)
			{
				filterList.Insert(0, item);
			}
		}

		private IModelBinder GetModelBinder(ParameterDescriptor parameterDescriptor)
		{
			return (parameterDescriptor.BindingInfo.Binder ?? this.Binders.GetBinder(parameterDescriptor.ParameterType));
		}

		private static Predicate<string> GetPropertyFilter(ParameterDescriptor parameterDescriptor)
		{
			ParameterBindingInfo bindingInfo = parameterDescriptor.BindingInfo;
			return propertyName => IsPropertyAllowed(propertyName, bindingInfo.Include.ToArray<string>(), bindingInfo.Exclude.ToArray<string>());
		}

		private static bool IsPropertyAllowed(string propertyName, string[] includeProperties, string[] excludeProperties)
		{
			bool flag = ((includeProperties == null) || (includeProperties.Length == 0)) || includeProperties.Contains<string>(propertyName, StringComparer.OrdinalIgnoreCase);
			bool flag2 = (excludeProperties != null) && excludeProperties.Contains<string>(propertyName, StringComparer.OrdinalIgnoreCase);
			return (flag && !flag2);
		}
	}
}
